package util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.PriorityQueue;

import util.Node.LastNode;
import util.Node.NodeState;
import util.PublicVars.Heuristic;

public class AStar implements Runnable {

	Thread myThread = new Thread(this, "aStar");
	HashMap<Long, Node> openListContent = new HashMap<Long, Node>();
	PriorityQueue<Node> openList = new PriorityQueue<Node>();
	HashMap<Long, Node> closedList = new HashMap<Long, Node>();
	List<LastNode> finalPath = new ArrayList<LastNode>();

	private int startX;
	private int startY;

	private int endX;
	private int endY;

	private int x;
	private int y;

	public List<LastNode> getPath() {

		findStartAndEndPoint(PublicVars.getNodeNet());
		doHeuristicWork();

		openList.add(PublicVars.getNodeNet()[startX][startY]);
		openListContent.put((((long) startX) << 32) + startY, PublicVars.getNodeNet()[startX][startY]);
		PublicVars.getNodeNet()[startX][startY].setState(NodeState.OPENLIST);

		int counter = 0;

		do {
			counter++;
			if (openList.isEmpty())
				break;
			Node n = openList.peek();
			

			System.out.println(n);
			
			x = n.getX();
			y = n.getY();

			closedList.put((((long) x) << 32) + y, n);
			n.setState(NodeState.CLOSEDLIST);

			openList.poll();
			openListContent.remove((((long) x) << 32) + y);

			// WEST
			if (isObstacle(x - 1, y) || isInClosedList(x - 1, y)) {
				// ignore
			} else if (!isInOpenList(x - 1, y)) {
				n.setLast(LastNode.EAST);

				PublicVars.getNodeNet()[x - 1][y].setgCosts(findNodeInClosedList(x, y).getgCosts() + 10);

				openList.add(PublicVars.getNodeNet()[x - 1][y]);
				openListContent.put((((long) x - 1) << 32) + y, PublicVars.getNodeNet()[x - 1][y]);
				findNodeInOpenList(x - 1, y).setState(NodeState.OPENLIST);
			} else {

			}

			// EAST
			if (isObstacle(x + 1, y) || isInClosedList(x + 1, y)) {
				// ignore
			} else if (!isInOpenList(x + 1, y)) {
				n.setLast(LastNode.WEST);

				PublicVars.getNodeNet()[x + 1][y].setgCosts(findNodeInClosedList(x, y).getgCosts() + 10);

				openList.add(PublicVars.getNodeNet()[x + 1][y]);
				openListContent.put((((long) x + 1) << 32) + y, PublicVars.getNodeNet()[x + 1][y]);
				findNodeInOpenList(x + 1, y).setState(NodeState.OPENLIST);
			} else {
				// code to do
			}

			// NORTH
			if (isObstacle(x, y - 1) || isInClosedList(x, y - 1)) {
				// ignore
			} else if (!isInOpenList(x, y - 1)) {
				n.setLast(LastNode.SOUTH);

				PublicVars.getNodeNet()[x][y - 1].setgCosts(findNodeInClosedList(x, y).getgCosts() + 10);

				openList.add(PublicVars.getNodeNet()[x][y - 1]);
				openListContent.put((((long) x) << 32) + (y - 1), PublicVars.getNodeNet()[x][y - 1]);
				findNodeInOpenList(x, y - 1).setState(NodeState.OPENLIST);
			} else {
				// code to do
			}

			// SOUTH
			if (isObstacle(x, y + 1) || isInClosedList(x, y + 1)) {
				// ignore
			} else if (!isInOpenList(x, y + 1)) {
				n.setLast(LastNode.NORTH);

				PublicVars.getNodeNet()[x][y + 1].setgCosts(findNodeInClosedList(x, y).getgCosts() + 10);

				openList.add(PublicVars.getNodeNet()[x][y + 1]);
				openListContent.put((((long) x) << 32) + (y + 1), PublicVars.getNodeNet()[x][y + 1]);
				findNodeInOpenList(x, y + 1).setState(NodeState.OPENLIST);
			} else {
				// code to do
			}

			// NORTHEAST
			if (isObstacle(x + 1, y - 1) || isInClosedList(x + 1, y - 1)) {
				// ignore
			} else if (!isInOpenList(x + 1, y - 1)) {
				n.setLast(LastNode.SOUTHWEST);

				PublicVars.getNodeNet()[x + 1][y - 1].setgCosts(findNodeInClosedList(x, y).getgCosts() + 14);

				openList.add(PublicVars.getNodeNet()[x + 1][y - 1]);
				openListContent.put((((long) (x + 1)) << 32) + (y - 1), PublicVars.getNodeNet()[x + 1][y - 1]);
				findNodeInOpenList(x + 1, y - 1).setState(NodeState.OPENLIST);
			} else {
				// code to do
			}

			// SOUTHEAST
			if (isObstacle(x + 1, y + 1) || isInClosedList(x + 1, y + 1)) {
				// ignore
			} else if (!isInOpenList(x + 1, y + 1)) {
				n.setLast(LastNode.NORTHWEST);

				PublicVars.getNodeNet()[x + 1][y + 1].setgCosts(findNodeInClosedList(x, y).getgCosts() + 14);

				openList.add(PublicVars.getNodeNet()[x + 1][y + 1]);
				openListContent.put((((long) (x + 1)) << 32) + (y + 1), PublicVars.getNodeNet()[x + 1][y + 1]);
				findNodeInOpenList(x + 1, y + 1).setState(NodeState.OPENLIST);
			} else {
				// code to do
			}

			// SOUTHWEST
			if (isObstacle(x - 1, y + 1) || isInClosedList(x - 1, y + 1)) {
				// ignore
			} else if (!isInOpenList(x - 1, y + 1)) {
				n.setLast(LastNode.NORTHEAST);

				PublicVars.getNodeNet()[x - 1][y + 1].setgCosts(findNodeInClosedList(x, y).getgCosts() + 14);

				openList.add(PublicVars.getNodeNet()[x - 1][y + 1]);
				openListContent.put((((long) (x - 1)) << 32) + (y + 1), PublicVars.getNodeNet()[x - 1][y + 1]);
				findNodeInOpenList(x - 1, y + 1).setState(NodeState.OPENLIST);
			} else {
				// code to do
			}

			// NORTHWEST
			if (isObstacle(x - 1, y - 1) || isInClosedList(x - 1, y - 1)) {
				// ignore
			} else if (!isInOpenList(x - 1, y - 1)) {
				n.setLast(LastNode.SOUTHEAST);

				PublicVars.getNodeNet()[x - 1][y - 1].setgCosts(findNodeInClosedList(x, y).getgCosts() + 14);

				openList.add(PublicVars.getNodeNet()[x - 1][y - 1]);
				openListContent.put((((long) (x - 1)) << 32) + (y - 1), PublicVars.getNodeNet()[x - 1][y - 1]);
				findNodeInOpenList(x - 1, y - 1).setState(NodeState.OPENLIST);
			} else {
				// code to do
			}

			try {
				Thread.sleep(PublicVars.getAnimSpeed());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			if (x == endX && y == endY)
				break;
			if (counter >= 10000)
				break;

		} while (true);

		// finish path

		int newX = endX;
		int newY = endY;
		int counter2 = 0;
		do {
			counter2++;
			Node node;
			if (counter >= 10000)
				break;

			node = findNodeInClosedList(newX, newY);
			//System.out.println(node);
			finalPath.add(node.getLast());
			System.out.println(counter2);
			try {
				switch (node.getLast()) {
				case NORTH:
					newY--;
					break;
				case WEST:
					newX--;
					break;
				case EAST:
					newX++;
					break;
				case SOUTH:
					newY++;
					break;
				case SOUTHEAST:
					newY++;
					newX++;
					break;
				case SOUTHWEST:
					newY++;
					newX--;
					break;
				case NORTHEAST:
					newY--;
					newX++;
					break;
				case NORTHWEST:
					newY--;
					newX--;
					break;
				default:
					break;
				}
			} catch (NullPointerException noLast) {
				System.out.println(finalPath + " " + counter2);
				break;
			}

		} while (true);
		System.out.println(finalPath);
		return finalPath;
	}

	private void findStartAndEndPoint(Node[][] nodeNet) {

		for (int i = 0; i < nodeNet.length; i++) {
			for (int j = 0; j < nodeNet[i].length; j++) {
				if (nodeNet[i][j].isStart()) {
					startX = i;
					startY = j;
				}
				if (nodeNet[i][j].isTarget()) {
					endX = i;
					endY = j;
				}
			}
		}

	}

	private void doHeuristicWork() {
		for (int nX = 0; nX < PublicVars.getNodeNet().length; nX++) {
			for (int nY = 0; nY < PublicVars.getNodeNet()[nX].length; nY++) {
				int hCosts = 0;
				switch (PublicVars.getHeuristic()) {
				case DIAGONALSHORTCUT:
					int xDist = Math.abs(nX - endX);
					int yDist = Math.abs(nY - endY);
					if (xDist > yDist) {
						hCosts = 14 * yDist + 10 * (xDist - yDist);
					} else {
						hCosts = 14 * xDist + 10 * (yDist - xDist);
					}
					break;
				case AIRLINE:
					hCosts = (int) ((Math.sqrt((nX - endX) * (nX - endX) + (nY - endY) * (nY - endY))));
					break;
				case MANHATTAN:

					break;
				default:
					break;
				}

				PublicVars.getNodeNet()[nX][nY].sethCosts(hCosts);
				PublicVars.getNodeNet()[nX][nY].setgCosts(1);

			}
		}
	}

	private Node findNodeInClosedList(int sX, int sY) {
		return closedList.get((((long) sX) << 32) + sY);
	}

	private Node findNodeInOpenList(int sX, int sY) {
		return openListContent.get((((long) sX) << 32) + sY);
	}

	private boolean isInOpenList(int sX, int sY) {
		return openListContent.containsKey((((long) sX) << 32) + sY);
	}

	private boolean isInClosedList(int sX, int sY) {
		return closedList.containsKey((((long) sX) << 32) + sY);
	}

	private boolean isObstacle(int sX, int sY) {
		if (sX > PublicVars.getFieldWidth() - 1 || sX < 0 || sY > PublicVars.getFieldHeight() - 1 || sY < 0) {
			return true;
		}
		return PublicVars.getNodeNet()[sX][sY].isObstacle();
	}

	@Override
	public void run() {
		PublicVars.setCurrentPath(getPath());
		PublicVars.setPathFindingFinished(true);
		System.out.println("A* Finished");

	}

	public Thread getMyThread() {
		return myThread;
	}

}
